package sample.algo.sistr.algosample.algos.graphs;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import java.util.Iterator;
import java.util.LinkedList;

import sample.algo.sistr.algosample.R;
import sample.algo.sistr.algosample.algos.Algo;

/**
 * Created by sistr on 28/09/14.
 */
public class AlgoDijkstra extends Algo {

    private GraphView graphView;
    private final Graph graph;

    private Node start;
    private Node end;

    private final LinkedList<Node> nodesToBeChecked;

    private enum State {
        INITIALISATION,
        MAIN_LOOP,
        RECONSTRUCT_PATH,
        ENDED
    }

    private State state;


    public AlgoDijkstra(Context context) {
        super("Dijkstra", context);
        graphView = null;

        nodesToBeChecked = new LinkedList<Node>();
        graph = new Graph();

        // Create all nodes
        start = new Node(0, 0.1f, 0.1f);
        Node node1 = new Node(1, 0.5f, 0.1f);
        Node node2 = new Node(2, 0.9f, 0.1f);
        Node node3 = new Node(3, 0.5f, 0.5f);
        Node node4 = new Node(4, 0.9f, 0.5f);
        Node node5 = new Node(5, 0.1f, 0.9f);
        Node node6 = new Node(6, 0.5f, 0.9f);
        end   = new Node(7, 0.9f, 0.9f);

        // Create all edges
        start.AddEdge(node1, 1);
        start.AddEdge(node5, 1);

        node1.AddEdge(start, 1);
        node1.AddEdge(node2, 1);
        node1.AddEdge(node3, 1);

        node2.AddEdge(node4, 1);
        node2.AddEdge(node3, 1);

        node3.AddEdge(node2, 1);
        node3.AddEdge(node4, 1);
        node3.AddEdge(node6, 1);

        node4.AddEdge(end, 1);

        node5.AddEdge(node6, 1);

        node6.AddEdge(end, 1);

        // Add all nodes to the graph
        graph.AddNode(start);
        graph.AddNode(node1);
        graph.AddNode(node2);
        graph.AddNode(node3);
        graph.AddNode(node4);
        graph.AddNode(node5);
        graph.AddNode(node6);
        graph.AddNode(end);

        state = State.INITIALISATION;
    }

    @Override
    public View GetDisplay() {
        ViewGroup root = (ViewGroup)((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.algo_dijkstra, null);
        graphView = (GraphView)root.findViewById(R.id.graph_view);
        graphView.SetGraph(graph);

        final Button nextButton = (Button) root.findViewById(R.id.button_next);
        nextButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                switch(state) {
                    case INITIALISATION:
                        Initialise();
                        return;
                    case MAIN_LOOP:
                        MainLoop();
                        return;
                    case RECONSTRUCT_PATH:
                        ReconstructPath();
                        return;
                    case ENDED:
                        nextButton.setClickable(false);
                        return;
                }
            }
        });

        Button restartButton = (Button) root.findViewById(R.id.button_restart);
        restartButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Initialise();
                nextButton.setClickable(true);
            }
        });


        return root;
    }



    private void MainLoop() {

        if(!nodesToBeChecked.isEmpty()) {

            // Get the closest not checked node
            Node minNode = null;
            float minVal = Float.MAX_VALUE;
            for(Node n : nodesToBeChecked) {
                if(n.reaching_cost >= 0 && n.reaching_cost < minVal) {
                    minNode = n;
                    minVal = n.reaching_cost;
                }
            }

            if(minNode != null){
                nodesToBeChecked.remove(minNode);

                // Iterate over minNode neighbors
                for (Edge e : minNode.out_edges) {
                    if (e.end.reaching_cost < 0 || e.end.reaching_cost > minNode.reaching_cost + e.weight) {
                        e.end.reaching_cost = minNode.reaching_cost + e.weight;
                        e.end.predecessor = minNode;
                    }

                }
            }
        } else {
            state = State.RECONSTRUCT_PATH;
        }

        graphView.invalidate();
    }

    private void Initialise() {
        nodesToBeChecked.clear();

        Iterator<Node> nodeIte = graph.start();
        while(nodeIte.hasNext()) {
            Node n = nodeIte.next();

            n.reaching_cost = -1;
            n.predecessor = null;
            n.state = Node.State.NORMAL;

            nodesToBeChecked.add(n);
        }
        start.reaching_cost = 0;

        state = State.MAIN_LOOP;
        graphView.invalidate();
    }

    private void ReconstructPath() {

        if(end.predecessor != null) {
            Node n = end;
            do{
                n.state = Node.State.HIGHLIGHTED;
                n = n.predecessor;
            } while(n != null);
        }

        state = State.ENDED;

        graphView.invalidate();
    }

}
